package com.paw.geo;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;

import com.alibaba.fastjson.JSON;

/**
 * http://www.movable-type.co.uk/scripts/geohash.html
 * 可以到 http://geohash.co/ 进行geohash编码，以确定自己代码是否写错
 *
 * GeoHash长度表示的精度
 * 1	≤ 5,000km	×	5,000km
 * 2	≤ 1,250km	×	625km
 * 3	≤ 156km	×	156km
 * 4	≤ 39.1km	×	19.5km
 * 5	≤ 4.89km	×	4.89km
 * 6	≤ 1.22km	×	0.61km
 * 7	≤ 153m	×	153m
 * 8	≤ 38.2m	×	19.1m
 * 9	≤ 4.77m	×	4.77m
 * 10	≤ 1.19m	×	0.596m
 * 11	≤ 149mm	×	149mm
 * 12	≤ 37.2mm	×	18.6mm
 **/
public class GeoHash {
  public static final double MINLAT = -90;
  public static final double MAXLAT = 90;
  public static final double MINLNG = -180;
  public static final double MAXLNG = 180;

  // 假设我们计算的key值是8位，那么二进制位数就是8*5 = 40，所以经纬度分别是20位。我们以纬度为例，纬度会均分20次
  private static int numbits = 4 * 5; //经纬度单独编码长度

  private static double minLat;
  private static double minLng;

  private final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
      '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p',
      'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

  //定义编码映射关系
  final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
  //初始化编码映射内容
  static {
    int i = 0;
    for (char c : digits) {
      lookup.put(c, i++);
    }
  }

  public GeoHash(){
    setMinLatLng();
  }

  public String encode(double lat, double lon) {
    BitSet latbits = getBits(lat, -90, 90);
    BitSet lonbits = getBits(lon, -180, 180);
    StringBuilder buffer = new StringBuilder();
    for (int i = 0; i < numbits; i++) {
      buffer.append( (lonbits.get(i))?'1':'0');
      buffer.append( (latbits.get(i))?'1':'0');
    }
    String code = base32(Long.parseLong(buffer.toString(), 2));
    //Log.i("okunu", "encode  lat = " + lat + "  lng = " + lon + "  code = " + code);
    return code;
  }

  public List<String> getArroundGeoHash(double lat, double lon){
    //Log.i("okunu", "getArroundGeoHash  lat = " + lat + "  lng = " + lon);
    ArrayList<String> list = new ArrayList<>();
    double uplat = lat + minLat;
    double downLat = lat - minLat;

    double leftlng = lon - minLng;
    double rightLng = lon + minLng;

    String leftUp = encode(uplat, leftlng);
    list.add(leftUp);

    String leftMid = encode(lat, leftlng);
    list.add(leftMid);

    String leftDown = encode(downLat, leftlng);
    list.add(leftDown);

    String midUp = encode(uplat, lon);
    list.add(midUp);

    String midMid = encode(lat, lon);
    list.add(midMid);

    String midDown = encode(downLat, lon);
    list.add(midDown);

    String rightUp = encode(uplat, rightLng);
    list.add(rightUp);

    String rightMid = encode(lat, rightLng);
    list.add(rightMid);

    String rightDown = encode(downLat, rightLng);
    list.add(rightDown);

    //Log.i("okunu", "getArroundGeoHash list = " + list.toString());
    return list;
  }

  //根据经纬度和范围，获取对应的二进制
  private BitSet getBits(double lat, double floor, double ceiling) {
    BitSet buffer = new BitSet(numbits);
    for (int i = 0; i < numbits; i++) {
      double mid = (floor + ceiling) / 2;
      if (lat >= mid) {
        buffer.set(i);
        floor = mid;
      } else {
        ceiling = mid;
      }
    }
    return buffer;
  }

  //将经纬度合并后的二进制进行指定的32位编码
  private String base32(long i) {
    char[] buf = new char[65];
    int charPos = 64;
    boolean negative = (i < 0);
    if (!negative){
      i = -i;
    }
    while (i <= -32) {
      buf[charPos--] = digits[(int) (-(i % 32))];
      i /= 32;
    }
    buf[charPos] = digits[(int) (-i)];
    if (negative){
      buf[--charPos] = '-';
    }
    return new String(buf, charPos, (65 - charPos));
  }

  private void setMinLatLng() {
    minLat = MAXLAT - MINLAT;
    for (int i = 0; i < numbits; i++) {
      minLat /= 2.0;
    }
    minLng = MAXLNG - MINLNG;
    for (int i = 0; i < numbits; i++) {
      minLng /= 2.0;
    }
  }

  //根据二进制和范围解码
  private double decode(BitSet bs, double floor, double ceiling) {
    double mid = 0;
    for (int i=0; i<bs.length(); i++) {
      mid = (floor + ceiling) / 2;
      if (bs.get(i)) {
        floor = mid;
      } else {
        ceiling = mid;
      }
    }
    return mid;
  }

  //对编码后的字符串解码
  public double[] decode(String geohash) {
    StringBuilder buffer = new StringBuilder();
    for (char c : geohash.toCharArray()) {
      int i = lookup.get(c) + 32;
      buffer.append( Integer.toString(i, 2).substring(1) );
    }

    BitSet lonset = new BitSet();
    BitSet latset = new BitSet();

    //偶数位，经度
    int j =0;
    for (int i=0; i< numbits*2;i+=2) {
      boolean isSet = false;
      if ( i < buffer.length() ) {
        isSet = buffer.charAt(i) == '1';
      }
      lonset.set(j++, isSet);
    }

    //奇数位，纬度
    j=0;
    for (int i=1; i< numbits*2;i+=2) {
      boolean isSet = false;
      if ( i < buffer.length() ) {
        isSet = buffer.charAt(i) == '1';
      }
      latset.set(j++, isSet);
    }

    double lon = decode(lonset, -180, 180);
    double lat = decode(latset, -90, 90);

    return new double[] {lat, lon};
  }

  public static void main(String[] args)  throws Exception{
    GeoHash geohash = new GeoHash();
        String s1 = geohash.encode(40.222012, 116.248283);
        String s = geohash.encode(39.999375,116.402843);
        String s2 = geohash.encode(39.99932,116.3967);
        System.out.println(s);
        System.out.println(s2);
    List<String> arroundGeoHash = geohash.getArroundGeoHash(40.222012, 116.248283);
//        double[] geo = geohash.decode(s);
        System.out.println(JSON.toJSONString(arroundGeoHash));
  }
}